/**
 *   Copyright 2012-2019 Wicked Charts (http://github.com/adessoAG/wicked-charts)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package de.adesso.wickedcharts.highcharts.options;

import com.fasterxml.jackson.annotation.JsonIgnore;
import de.adesso.wickedcharts.highcharts.options.color.ColorReference;
import de.adesso.wickedcharts.highcharts.options.heatmap.ColorAxis;
import de.adesso.wickedcharts.highcharts.options.series.Series;

import java.io.Serializable;
import java.util.*;

/**
 * This is the main class for configuring a chart. The structure of this class
 * is designed to be very close to the Highcharts options object. For
 * explanations of all options, please consult the Highcharts API reference.
 *
 * The options object will be serialized to a JSON object which is passed into
 * the Highcharts Javascript library. Options that are not set (i.e. that are
 * null), are not serialized into JSON, so that the default values of the
 * Highcharts library will be used.
 *
 * @author Tom Hombergs (tom.hombergs@gmail.com)
 * @see <a href="http://api.highcharts.com/highcharts">http://api.highcharts.com/highcharts</a>
 */
public class Options implements Serializable {

    @JsonIgnore
    private Map<String, List<IProcessableOption>> processingRegistry = new HashMap<String, List<IProcessableOption>>();

    private static final long serialVersionUID = 1L;

    private ColorAxis colorAxis;

    private ChartOptions chart;

    private List<Series<?>> series;

    private List<ColorReference> colors;

    private Title title;

    private Title subtitle;

    private List<Axis> xAxis;

    private List<Axis> yAxis;

    private Legend legend;

    private ExportingOptions exporting;

    private CreditOptions credits;

    private PlotOptionsChoice plotOptions;

    private Tooltip tooltip;

    private Global global;

    private Pane pane;

    private Scrollbar scrollbar;

    @JsonIgnore
    private Labels labels;

    private Loading loading;

    private Navigation navigation;

    public Options() {

    }

    /**
     * Constructs an {@link Options} object initialized with the configuration of
     * another {@link Options} object.
     *
     * @param template the {@link Options} object to copy the configuration from.
     */
    public Options(final Options template) {
        copyFrom(template);
    }

    public Options addColor(final ColorReference color) {
        if (this.colors == null) {
            this.colors = new ArrayList<ColorReference>();
        }
        this.colors.add(color);
        return this;
    }

    public Options addSeries(final Series<?> series) {
        if (this.series == null) {
            this.series = new ArrayList<Series<?>>();
        }
        this.series.add(series);
        return this;
    }

    /**
     * Adds an XAxis to the chart. You can use {@link #setxAxis(Axis)} if you want
     * to define a single axis only.
     *
     * @param xAxis the XAxis to add.
     * @return the {@link Options} object for chaining.
     */
    public Options addxAxis(final Axis xAxis) {
        if (this.getxAxis() == null) {
            this.setxAxis(new ArrayList<Axis>());
        }
        this.getxAxis().add(xAxis);
        return this;
    }

    /**
     * Adds an YAxis to the chart. You can use {@link #setyAxis(Axis)} if you want
     * to define a single axis only.
     *
     * @param yAxis the YAxis to add.
     * @return the {@link Options} object for chaining.
     */
    public Options addyAxis(final Axis yAxis) {
        if (this.getyAxis() == null) {
            this.setyAxis(new ArrayList<Axis>());
        }
        this.getyAxis().add(yAxis);
        return this;
    }

    public void clearSeries() {
        this.series = new ArrayList<Series<?>>();
    }

    public void copyFrom(final Options template) {
        this.chart = template.chart;
        this.colors = template.colors;
        this.credits = template.credits;
        this.exporting = template.exporting;
        this.global = template.global;
        this.labels = template.labels;
        this.legend = template.legend;
        this.loading = template.loading;
        this.navigation = template.navigation;
        this.pane = template.pane;
        this.plotOptions = template.plotOptions;
        this.processingRegistry = template.processingRegistry;
        this.series = template.series;
        this.subtitle = template.subtitle;
        this.title = template.title;
        this.tooltip = template.tooltip;
        this.setxAxis(template.getxAxis());
        this.setyAxis(template.getyAxis());
        this.setScrollbar(template.getScrollbar());
    }

    public ChartOptions getChart() {
        return this.chart;
    }

    public ColorAxis getColorAxis() {
        return colorAxis;
    }

    @JsonIgnore
    public ChartOptions getChartOptions() {
        return this.chart;
    }

    public List<? extends ColorReference> getColors() {
        return this.colors;
    }

    public CreditOptions getCreditOptions() {
        return this.credits;
    }

    public CreditOptions getCredits() {
        return this.credits;
    }

    public ExportingOptions getExporting() {
        return this.exporting;
    }

    public Global getGlobal() {
        return this.global;
    }

    public Labels getLabels() {
        return this.labels;
    }

    public Legend getLegend() {
        return this.legend;
    }

    public Loading getLoading() {
        return this.loading;
    }

    /**
     * Returns the {@link IProcessableOption}s that were marked by calling
     * {@link #markForProcessing(IProcessableOption)} with the given key.
     *
     * @param processingKey the key with which the options in questions were marked.
     * @return list of marked {@link IProcessableOption}s, or an empty list, if
     * none were marked
     */
    public List<IProcessableOption> getMarkedForProcessing(final String processingKey) {
        List<IProcessableOption> result = this.processingRegistry.get(processingKey);
        if (result == null) {
            return new ArrayList<IProcessableOption>();
        } else {
            return result;
        }
    }

    public Navigation getNavigation() {
        return this.navigation;
    }

    public Pane getPane() {
        return this.pane;
    }

    public PlotOptionsChoice getPlotOptions() {
        return this.plotOptions;
    }

    public Scrollbar getScrollbar(){
        return this.scrollbar;
    }

    @SuppressWarnings("rawtypes")
    public List<? extends Series> getSeries() {
        return this.series;
    }

    /**
     * Returns the single xAxis. If there is more than one xAxis defined, the
     * first is returned.
     *
     * @return the single xAxis.
     */
    @JsonIgnore
    public Axis getSingleXAxis() {
        if (this.getxAxis() == null) {
            return null;
        } else {
            return this.getxAxis().get(0);
        }
    }

    /**
     * Returns the single yAxis. If there is more than one yAxis defined, the
     * first is returned.
     *
     * @return the single yAxis.
     */
    @JsonIgnore
    public Axis getSingleYAxis() {
        if (this.getyAxis() == null) {
            return null;
        } else {
            return this.getyAxis().get(0);
        }
    }

    public Title getSubtitle() {
        return this.subtitle;
    }

    public Title getTitle() {
        return this.title;
    }

    public Tooltip getTooltip() {
        return this.tooltip;
    }

    public List<Axis> getxAxis() {
        return this.xAxis;
    }

    public List<Axis> getyAxis() {
        return this.yAxis;
    }

    public void markForProcessing(final IProcessableOption option) {
        List<IProcessableOption> list = this.processingRegistry.get(option.getProcessingKey());
        if (list == null) {
            list = new ArrayList<IProcessableOption>();
            this.processingRegistry.put(option.getProcessingKey(), list);
        }
        list.add(option);
    }

    public Options setChart(final ChartOptions chart) {
        this.chart = chart;
        return this;
    }

    public Options setChartOptions(final ChartOptions chart) {
        this.chart = chart;
        return this;
    }

    public Options setColors(final ColorReference... colors) {
        this.colors = Arrays.asList(colors);
        return this;
    }

    public Options setColors(final List<ColorReference> colors) {
        this.colors = colors;
        return this;
    }

    public Options setCreditOptions(final CreditOptions credits) {
        this.credits = credits;
        return this;
    }

    public Options setCredits(final CreditOptions credits) {
        this.credits = credits;
        return this;
    }

    public Options setExporting(final ExportingOptions exporting) {
        this.exporting = exporting;
        return this;
    }

    public Options setGlobal(final Global global) {
        this.global = global;
        markForProcessing(global);
        return this;
    }

    public Options setLabels(final Labels labels) {
        this.labels = labels;
        return this;
    }

    public Options setLegend(final Legend legend) {
        this.legend = legend;
        return this;
    }

    public Options setLoading(final Loading loading) {
        this.loading = loading;
        return this;
    }

    public Options setNavigation(final Navigation navigation) {
        this.navigation = navigation;
        return this;
    }

    public Options setPane(final Pane pane) {
        this.pane = pane;
        return this;
    }

    public Options setPlotOptions(final PlotOptionsChoice plotOptions) {
        this.plotOptions = plotOptions;
        return this;
    }

    public Options setScrollbar(final Scrollbar scrollbar) {
        this.scrollbar = scrollbar;
        return this;
    }

    public Options setSeries(final List<Series<?>> series) {
        this.series = series;
        return this;
    }

    public Options setSubtitle(final Title subtitle) {
        this.subtitle = subtitle;
        return this;
    }

    public Options setTitle(final Title title) {
        this.title = title;
        return this;
    }

    public Options setTooltip(final Tooltip tooltip) {
        this.tooltip = tooltip;
        return this;
    }

    /**
     * Sets the single xAxis. If you need to define more than one xAxis use
     * {@link #addxAxis(Axis)}. If there are multiple Axes defined when calling
     * this method, only the specified axis will be defined afterwards.
     *
     * @param xAxis the single xAxis of the chart
     * @return the {@link Options} object for chaining
     */
    public Options setxAxis(final Axis xAxis) {
        this.xAxis = new ArrayList<Axis>();
        this.xAxis.add(xAxis);
        return this;
    }

    public Options setxAxis(final List<Axis> xAxis) {
        this.xAxis = xAxis;
        return this;
    }

    /**
     * Sets the single yAxis. If you need to define more than one yAxis use
     * {@link #addyAxis(Axis)}. If there are multiple Axes defined when calling
     * this method, only the specified axis will be defined afterwards.
     *
     * @param yAxis the single yAxis of the chart
     * @return the {@link Options} object for chaining
     */
    public Options setyAxis(final Axis yAxis) {
        this.yAxis = new ArrayList<Axis>();
        this.yAxis.add(yAxis);
        return this;
    }

    public Options setyAxis(final List<Axis> yAxis) {
        this.yAxis = yAxis;
        return this;
    }

    public Options setColorAxis(final ColorAxis colorAxis) {
        this.colorAxis = colorAxis;
        return this;
    }

}
